#include <dbus-c++/dbus.h>
#include "dbus-service.h"
#include "glue-dbus-server.h"

#include <iostream>
#include <errno.h>
#include <string.h>

#include "blist.h"
#include "utils.h"

using namespace std;

/*
 * Data structure of Talks' table.
 * 
 * role := "invitor" or "acceptor"
 */

struct Talk {
	int tid;
	PurpleBuddy *contact;
	string status;
	string serial;
	string app;
	string role;
};

vector<Talk*> TalkTable;

void showTalks(){
	vector<Talk*>::iterator iter;
	cerr << endl << "========================================================" << endl;
	cerr << "|" 
	<< "tid" << "\t|" 
	<< "contact" << "\t|" 
	<< "status" << "\t|" 
	<< "serial" << "\t|" 
	<< "app" << "\t|" 
	<< "role" << "\t|" 
	<< endl;

	for ( iter = TalkTable.begin(); iter != TalkTable.end(); iter++ ){
		cerr << "|"      
		<< (*iter)->tid  << "\t|"      
		<< (*iter)->contact << "\t|"      
		<< (*iter)->status  << "\t|"      
		<< (*iter)->serial << "\t|"      
		<< (*iter)->app << "\t|"      
		<< (*iter)->role << "\t|"      
		<< endl;
	} 
	cerr << "========================================================" << endl;
}

Talk* getTalk( int tid ){
	Talk* result = NULL;

	vector<Talk*>::iterator iter;
	for ( iter = TalkTable.begin(); iter != TalkTable.end(); iter++ ){
		if ( (*iter)->tid == tid ){
			result = (*iter);
		}
	} 
	
	return result;
}

void delTalk( int tid ){
	vector<Talk*>::iterator iter;
	for ( iter = TalkTable.begin(); iter != TalkTable.end(); iter++ ){
		if ( (*iter)->tid == tid ){
			delete (*iter);
			TalkTable.erase(iter);
		}
	} 
}

int get_tid_from_table ( string serial, string acct_str, string buddy_str ){
	int result = 0;

	vector<Talk*>::iterator iter;
	for ( iter = TalkTable.begin(); iter != TalkTable.end(); iter++ ){

		Talk *talk = (*iter);
		PurpleAccount *acct = purple_buddy_get_account( talk->contact );

		string acct_name = string( purple_account_get_username( acct ) );
		string buddy_name = string( purple_buddy_get_name( talk->contact ) );

		/*
		cerr << " session: " << serial
                     << " from: " << acct_str
                     << " to: " << buddy_str
		     << endl;

		cerr << " serial: " << talk->serial
                     << " acct_name: " << acct_name
                     << " buddy_name: " << buddy_name
		     << endl;
		*/

		bool isSerialEqual = ( talk->serial == serial );
		bool isFromEqual = isJIDsimilar( acct_name, acct_str );
		bool isToEqual = isJIDsimilar( buddy_name, buddy_str );
		
		if ( isSerialEqual && isFromEqual && isToEqual ){
			result = talk->tid;
		}
	} 
	
	return result;
}

/*
 * Implementations of PiDBus
 */

PiDBus* PiDBus::_instance = 0;

PiDBus* PiDBus::getInstance()
{
	if ( _instance == 0 ) 
	{   
		try{
			DBus::_init_threading(); 
			DBus::BusDispatcher dispatcher;
			DBus::default_dispatcher = &dispatcher;
			DBus::Connection conn = DBus::Connection::SessionBus();
			conn.request_name(PIDBUS_BUSNAME);    
			_instance = new PiDBus(conn);
			dispatcher.enter();
		}   
		catch( DBus::Error e ){
			cerr << "InkDBus Error catched" << endl;
			_instance = NULL;

		}   
	}   
	return _instance;
}

PiDBus::PiDBus( DBus::Connection& connection )
: DBus::ObjectAdaptor(connection, PIDBUS_OBJPATH )
{
	// Pidgin plugin of D-Bus Service initialization
}

PiDBus::~PiDBus()
{
	// Pidgin plugin of D-Bus Service finalization 
}


void createTalk( string app, PurpleBuddy *buddy )
{
	Talk *talk = new Talk;

	int maxId = 0;
	vector<Talk*>::iterator iter;
	for ( iter = TalkTable.begin(); iter != TalkTable.end(); iter++ ){
		if( maxId < (*iter)->tid ){
			maxId = (*iter)->tid;
		}
	} 

	talk->tid = maxId+1;
	talk->app = app;
	talk->contact = buddy;
	talk->serial = randomString();
	talk->status = "";
	talk->role = "invitor";
	TalkTable.push_back( talk );

	PiDBus *pidbus = PiDBus::getInstance();
	pidbus->registeredTalk( talk->app, talk->tid );
}


void createTalkPassive( string app, string acct_name, string bddy_name, string serial )
{
	GSList *buddies = purple_blist_get_buddies();
	GSList *slist = buddies;

	vector<PurpleBuddy*> candidates;

	// 找出符合 < acct, buddy > 的 PurpleBuddy*
        while ( slist != NULL) {
                PurpleBuddy *buddy = static_cast<PurpleBuddy*>(slist->data);

		PurpleAccount *acct = purple_buddy_get_account( buddy );
		string account_name( purple_account_get_username( acct ) );
		string buddy_name( purple_buddy_get_name( buddy ) );

		cerr << account_name << "<==>" << acct_name << endl;
		cerr << buddy_name << "<==>" << bddy_name << endl;

		bool isAccountEqual = isJIDsimilar( account_name, acct_name );
		bool isBuddyEqual = isJIDsimilar( buddy_name, bddy_name );

		if( isAccountEqual && isBuddyEqual && PURPLE_BUDDY_IS_ONLINE(buddy)) {
			candidates.push_back( buddy );
		}

                slist = g_slist_next(slist);
        }

	if ( candidates.size() > 1 ){
		cerr << "duplicated matching..." << endl;
	}
	else if ( candidates.size() == 1 ){
		PurpleBuddy *found_buddy = candidates[0];

		Talk *talk = new Talk;

		int maxId = 0;
		vector<Talk*>::iterator iter;
		for ( iter = TalkTable.begin(); iter != TalkTable.end(); iter++ ){
			if( maxId < (*iter)->tid ){
				maxId = (*iter)->tid;
			}
		} 

		talk->tid = maxId+1;
		talk->app = app;
		talk->serial = serial;
		talk->contact = found_buddy;
		talk->status = "S_ANSWER";
		talk->role = "acceptor";
		TalkTable.push_back( talk );

		PiDBus *pidbus = PiDBus::getInstance();
		pidbus->recvTalk( talk->app, talk->tid );
	}	
}

void PiDBus::newTalk(const std::string& app)
{
	GSList *buddies = purple_blist_get_buddies();
	GSList *slist = buddies;

	vector<PurpleBuddy*> candidates;

        while ( slist != NULL) {
                PurpleBuddy *buddy = static_cast<PurpleBuddy*>(slist->data);

		if(PURPLE_BUDDY_IS_ONLINE(buddy)) {
			const char *name = purple_buddy_get_name( buddy );
			printf("<%s> is online.\n", name );
			candidates.push_back( buddy );
		}

                slist = g_slist_next(slist);
        }

	if( ! candidates.empty() ){
		showSelectContactDialog( candidates, app, createTalk );
	}
}


std::string PiDBus::getContact(const int32_t& tid)
{
	string result;

	Talk* talk = getTalk( tid );
	if( !talk ){ return result; }

	result = purple_buddy_get_name( talk->contact );

	return result;
}

std::string PiDBus::getAccount(const int32_t& tid)
{
	string result;

	Talk* talk = getTalk( tid );
	if( !talk ){ return result; }

	PurpleAccount *acct = purple_buddy_get_account( talk->contact );
	result = purple_account_get_username( acct );

	return result;
}

std::string PiDBus::getStatus(const int32_t& tid)
{
	string result;

	Talk *talk = getTalk( tid );

	if( !talk ){ return result; }

	result = talk->status;

	showTalks();

	return result;

}

bool PiDBus::dial(const int32_t& tid)
{
	bool result = true;

	Talk *talk = getTalk( tid );

	if( !talk ){ return false; }

	if ( talk->status == "" && talk->role == "invitor" ){
		talk->status = "S_DIAL";
		pidginSend( talk->contact, talk->serial, "<invite/>", talk->app );
	}
	else {
		; // do nothing
	}
	
	return true;
}

bool PiDBus::answer(const int32_t& tid)
{
	bool result = true;

	Talk *talk = getTalk( tid );

	if( !talk ){ return false; }

	if ( talk->status == "S_ANSWER" && talk->role == "acceptor" ){
		talk->status = "S_OK";
		pidginSend( talk->contact, talk->serial, "<accept/>", talk->app );
	}
	
	return true;
}

bool PiDBus::hangup(const int32_t& tid)
{
	bool result = true;

	Talk *talk = getTalk( tid );

	if( !talk ){ return false; }

	pidginSend( talk->contact, talk->serial, "<bye/>", talk->app );
	delTalk( tid );
	
	return true;

}

bool PiDBus::sendMessage(const int32_t& tid, const std::string& text)
{
	bool result = true;
	Talk *talk = getTalk( tid );

	if( !talk ){ return false; }

	if ( talk->status == "S_OK" && !talk->serial.empty() ){
		string data = text;
		pidginSend( talk->contact, talk->serial, data, talk->app );
	}
	else{
		result = false;
	}
	return result;

}

void processIncomingMessageFromPidgin( string to, string from, string serial, string app, string recv_data )
{
	// 看是不是有已經存在的連線
	int tid = get_tid_from_table( serial, to, from );
	cerr << tid << endl;

	// 若有，則傳送訊息
	if ( tid > 0 ){
		Talk *talk = getTalk( tid );

		if( recv_data == "<accept/>" ){
			if ( talk->role == "invitor" ){
				talk->status = "S_OK";
			}
		}
		else if( recv_data == "<bye/>" ){
			PiDBus* pidbus = PiDBus::getInstance();
			pidbus->hangup( tid );
		}
		else {
			if ( talk->status == "S_OK" ){
				PiDBus* pidbus = PiDBus::getInstance();
				pidbus->recvMessage( tid, recv_data );
			}
		}
	}
	// 若沒有存在的連線，則建立一個新的連線 for acceptor
	else {
		if ( recv_data == "<invite/>" ){
			cerr << "createTalkPassive" << endl;
			createTalkPassive( app, to, from, serial );
			cerr << "createTalkPassive..." << tid << endl;
		}
	}


}
